<?php

include("DBInterface.php");
require("FileMakerAPI.php");

define('FM_HOST', '147.162.42.62');
define('FM_NAME', 'ADAM_TEST');

class FileMakerDB implements DBInterface{
	//variabile contenente l'oggetto che fornisce punto d'accesso alle API di FileMaker	
	private $fmk=NULL;
	
	//variabile contenente l'unica istanza attuale della classe.
	private static $instance=NULL;
	private $currentUser="";
	
	//costruttore privato per permettere il design pattern Singleton
	private function __construct(){}
	
	/*ritorna l'istanza dell'oggetto, se ancora non c'è la crea*/
	public static function getInstance(){
		if(! self::$instance)
			self::$instance=new FileMakerDB();
		return self::$instance;
	}
	
  /* Metodo che instaura una connessione con il server
   *
   * @return response, un array che rispetta il pattern
   * $response['status'] : enum ["ok", "error"]
   * $response['content'] : stringa con un messaggio di errore nel caso sia andato storto qualcosa.
   * */
	public function connect($login,$passw){
			
		//se non c'è già l'oggetto filemaker lo crea
		if(! $this->fmk){
			$this->fmk = new FileMaker();
		    $this->fmk->setProperty('database', FM_NAME);
		    $this->fmk->setProperty('hostspec', FM_HOST);
		    $this->fmk->setProperty('username', $login);
		    $this->fmk->setProperty('password', $passw);
		}
		
		//altrimenti cambia solo le credenziali d'accesso
		else{
			$this->fmk->setProperty('username', $login);
		    $this->fmk->setProperty('password', $passw);
		}
		$currentUser=$login;
		
		//fa una prova per vedere se le credenziali sono corrette
		$response=null;
		$try=$this->fmk->listScripts();
		$error=$this->hasErrors($try);
		if ($error != false)
			return $error;
		
		$response['status']="ok";
		
		return $response;
	}
	
	/* Permette di avere un numero di identificazione univoco per l'utente.
	 * La criptazione avviene con md5, ottenuto tramite la concatenazione 
	 * di username e password.
	 * */
	public function getAuthenticationToken($username,$passw){
		$response['status']="ok";
		$response['content']=md5($username.$passw);
		return $response;
	}
	
	/* Restituisce un array associativo con il contenuto del record selezionato.
	 * $tableName è una stringa con il nome della tabella da interrogare.
	 * $id è il valore del record_id attribuito da filemaker al record
	 * 
	 * $response['status'] : enum ["ok", "error"]
     * $response['content'] : { campo1 => val1, campo2 => val2, ... , recid => record_id }
	 * */
	public function getRecord($tableName,$id){
		$record=$this->fmk->getRecordById($tableName,$id);
		
		//la query ha ritornato un errore
		$error=$this->hasErrors($record);
		if ($error != false) {
			return $error;
		}
		
		//altrimenti restituisce una risposta valida
		$response['status']="ok";
		$response['content']=$this->record2Array($record);
		return $response;
	}
	
	/* Restituisce un array associativo con il contenuto della selezionata.
	 * $tableName è una stringa con il nome della tabella da interrogare.
	 * 
	 * $response['status'] : enum ["ok", "error"]
     * $response['content'] : array("fields_name" => [campo1, campo2,...]
	 * 								"records" => array(
	 * 												   recid1 => array(valori record1)
	 * 												   recid2 => array(valori record2)
	 * 												  ) 
	 * 								)
	 * */
	public function getTable($tableName){
		
		$table=$this->fmk->newFindAllCommand($tableName)->execute();
		
		//la query ha ritornato un errore
		$error=$this->hasErrors($table);
		if ($error != false)
			return $error;
		
		//altrimenti restituisce una risposta valida
		$response['status']="ok";
		$response['content']=$this->table2Array($table);
		return $response;
	}
	
	/* Crea un nuovo record
	 * $tablename è il nome della tabella in cui inerire il record
	 * $fieldsArray è un array associativo "nome_campo" => valore
	 * 
	 * $response['status'] : enum ["ok", "error"]
     * $response['content'] : nuovo id attribuito da filemaker
	 * */
	public function saveRecord($tableName,$fieldsArray){
			
		//crea una query di inserimento nella tabella
		$query=$this->fmk->newAddCommand($tableName,$fieldsArray);
		$result=$query->execute();
		
		//inserimento fallito
		$error=$this->hasErrors($result);
		if ($error != false){
			$this->updateErrorTable($tableName, "add", $error['content']." Dettagli sul record: ".json_encode($fieldsArray));
			return $error;
		}
		$recid=$result->getFirstRecord()->getRecordId();
		$this->updateChangesTable($tableName, $recid, "add");
		//inserimento avvenuto
		return array("status" => "ok","content"=>$recid);
	}
	
	/* Modifica i valori esistenti di un record
	 * $tablename è il nome della tabella del record da modificare
	 * $recordId è l'id attribuito da FileMaker
	 * $fieldsArray è un array associativo "nome_campo" => valore
	 * */
	public function updateRecord($tableName,$recordId,$fieldsArray){
		//crea una query di modifica nella tabella
		$query=$this->fmk->newEditCommand($tableName,$recordId,$fieldsArray);
		$result=$query->execute();
		
		//modifica fallita
		$error=$this->hasErrors($result);
		if ($error != false){
			$this->updateErrorTable($tableName, "update", $error['content']." Dettagli sul record: ".json_encode($fieldsArray));
			return $error;
		}
		
		//modifica avvenuta
		$this->updateChangesTable($tableName, $recordId, "update");
		return array("status" => "ok");
	}
	
	public function deleteRecord($tableName,$id){
		//seleziona il record
		$record=$this->fmk->getRecordById($tableName,$id);

		//selezione fallita
		$error=$this->hasErrors($record);
		if ($error != false){
			$this->updateErrorTable($tableName, "del", $error['content']." Id record: $id");
			return $error;
		}
		
		$record->delete();
		
		//rimozione avvenuta
		$this->updateChangesTable($tableName, $id, "del");
		return array("status" => "ok");
	}
	
	/* Inserisce più record in una volta sola. La variabile $recordsData è un array così composto:
	 * array( "tabella1" => array([record1],[record2],...)
	 *        "tabella2" => array([record1],[record2],...)
	 * )
	 * 
	 * Restituisce un array associativo con l'esito dell'operazione
	 * $response['status'] : enum ["ok", "error"]
     * $response['content'] : array(0 => newId1, 1 => newId2,...) gli id sono in progressione, compaiono solo i record inseriti con successo
	 * */
	public function saveMultipleRecords($recordsData){
		
		//conta il numero di operazioni fallite
		$countFailed=0;
		$totalRecords=0;
		$result=null;
		$newRecids=array();
		//scorre le tabelle contenute nell'array
		foreach($recordsData as $table => $records){
			
			//per ogni tabella scorre i record in essa contenuti
			foreach($records as $record){
				
				
				//salva il record e controlla il risultato
				$result=$this->saveRecord($table, $record);
				
				//se va a buon fine annota il nuovo record id attribuito in un array associativo
				if($result['status']=="ok"){
					$newRecids[$totalRecords]=$result['content'];
				}
				//se fallisce incrementa il contatore 
				else
					$countFailed++;
				
				$totalrecords++;
			}
		}
		
		$response=array();
		
		if($countFailed == 0){
			$response['status']="ok";
			$response['content']=$newRecids;
		}
		else{
			$response['status']="error";
			$response['content']=$newRecids;
		}
		return $response;
	}
	
	/* Modifica più record una volta sola. La variabile $recordsData è un array così composto:
	 * array( "tabella1" => array(id1 => [record1],id2 => [record2],...)
	 *        "tabella2" => array(id1 => [record1],id2 => [record2],...)
	 * )
	 * Restituisce un array associativo con l'esito dell'operazione
	 * $response['status'] : enum ["ok", "error"]
     * $response['content'] : (solo se c'è un errore): array(id1,id2,....)
	 * */
	public function updateMultipleRecords($recordsData){
		$result=null;
		
		//salva gli id delle rei record falliti
		$failedId=array();
		
		//scorre le tabelle contenute nell'array
		foreach($recordsData as $table => $records){
			
			//per ogni tabella scorre i record in essa contenuti
			foreach($records as $id => $record){
				
				//salva il record e controlla il risultato
				$result=$this->updateRecord($table,$id,$record);
				
				//se fallisce inserisce l'id del record nell'array dei falliti 
				if($result['status']!="ok"){
					$failedId[]=$id;
				}
			}
		}
		
		$response=array();
		if(count($failedId) == 0)
			$response['status']="ok";
		else{
			$response['status']="error";
			$response['content']=$failedId;
		}
		return $response;
	}
	
	/* Cancella più record una volta sola. La variabile $recordsData è un array così composto:
	 * array( "tabella1" => array(id1, id2, ...)
	 *        "tabella2" => array(id1, id2, ...)
	 * )
	 * Restituisce un array associativo con l'esito dell'operazione
	 * $response['status'] : enum ["ok", "error"]
     * $response['content'] : (solo se c'è un errore): array(id1,id2,....)
	 * */
	public function deleteMultipleRecords($recordsData){
		$result=null;
		
		//salva gli id delle rei record falliti
		$failedId=array();
		
		//scorre le tabelle contenute nell'array
		foreach($recordsData as $table => $ids){
			
			//per ogni tabella scorre gli id dei campi da cancellare
			foreach($ids as $id){
				
				//salva il record e controlla il risultato
				$result=$this->deleteRecord($table,$id);
				
				//se fallisce inserisce l'id del record nell'array dei falliti 
				if($result['status']!="ok"){
					$failedId[]=$id;
				}
			}
		}
		
		$response=array();
		if(count($failedId) == 0)
			$response['status']="ok";
		else{
			$response['status']="error";
			$response['content']=$failedId;
		}
		return $response;
	}

	/* Ritorna il contenuto della tabella changesTable a partire da un determinato punto. 
	 * */
	public function getChangesTable($from_version=0){
		$query=$this->fmk->newFindCommand("Modifiche");
		$query->addFindCriterion("idMod",">=$from_version"); 
		$result=$query->execute();
		
		//la query ha ritornato un errore
		$error=$this->hasErrors($result);
		if ($error != false)
			return $error;
		
		//altrimenti restituisce una risposta valida
		$response['status']="ok";
		$response['content']=$this->table2Array($result);
		return $response;
		
	}
	public function getEntireDB(){
		$db=array();
		//recuperiamo il numero di versione attuale
		/*$query=$this->fmk->newFindAllCommand("Modifiche");
		$query->addSortRule("idMod",1,FILEMAKER_SORT_DESCEND);
		$query->setRange(0,1);
		$result=$query->execute();
		$records=$result->getRecords();
		$$db['version']=$records[0]->getField("idMod");*/
		$db['version']=1;
		
		//Prendiamo ad una ad una tutte le tabelle. Prima quelle senza dipendenze, poi le altre
		
		
		//US
		$res=$this->getTable("Formato per il WEB");
		if($res['status']!="ok")
			return $res;
		else
			$db['us']=$res['content'];
		echo "US<br>";
		
		//Diario
		$res=$this->getTable("Diario");
		if($res['status']!="ok")
			return $res;
		else
			$db['diario']=$res['content'];
		echo "Diario<br>";
		
		//Citazioni nel diario di US
		$res=$this->getTable("Citazioni nel diario di US");
		if($res['status']!="ok")
			return $res;
		else
			$db['citazionidiario']=$res['content'];
		
		//Contesti
		$res=$this->getTable("Contesti di US");
		if($res['status']!="ok")
			return $res;
		else
			$db['contesti']=$res['content'];
		
		//Citazioni nei contesti di us
		$res=$this->getTable("Citazioni nei contesti di us");
		if($res['status']!="ok")
			return $res;
		else
			$db['citazionicontesti']=$res['content'];
		
		//Campioni
		$res=$this->getTable("Campioni");
		if($res['status']!="ok")
			return $res;
		else
			$db['campioni']=$res['content'];
		
		//Reperti contabilizzati
		$res=$this->getTable("Reperti contabilizzati");
		if($res['status']!="ok")
			return $res;
		else
			$db['reperticontabilizzati']=$res['content'];
		
		//Reperti inventariati
		$res=$this->getTable("Reperti inventariati");
		if($res['status']!="ok")
			return $res;
		else
			$db['repertiinventariati']=$res['content'];
		
		return array("status" => "ok", "content" => $db);
	}
	
	
	/* Ritorna una risposta di errore con informazioni indicate sull'oggetto $FM_Error*/
	private function hasErrors($FM_Error){
		
		if ($this->fmk->isError($FM_Error)) {
			return array('status' => 'error', 'content' => $FM_Error->getMessage());
		} 
		//else if($FM_Error->getFoundSetCount()==0){
		//	return array('status' => 'error', 'content' => "Il database ha restituito una risposta vuota.");
		//}
		else if ($FM_Error === NULL) {
			return array('status' => 'error', 'content' => "Errore nell'esecuzione della query.");
		}
		else {
			return false;
		}
	}
	
	private function record2Array($record){
		$assoc_record=array();
		
		//recupera i nomi dei campi del record
		$fields=$record->getFields();
		
		//per ogni campo, crea una entry nome_campo => valore
		foreach($fields as $field){
			$assoc_record[$field]=$record->getField($field);
		}
		
		//aggiungi l'id attribuito da filemaker
		$assoc_record['recid']=$record->getRecordId();
		return $assoc_record;	
	}
	
	private function table2Array($table){
		$assoc_table=array();
		
		//recupera i nomi dei campi del layout della tabella
		$assoc_table['fields_name']=$table->getFields();
		
		//recupera l'array dei record
		$records=$table->getRecords();
		$map=array();
		
		//salva ogni record nella map indicizzata dal loro id:
		//$map[id]=array(val1, val2, ...)
		foreach($records as $record){
			
			$content=array();
			//preleva il valore di tutti i campi
			foreach($assoc_table['fields_name'] as $field){
				$content[]=$record->getField($field);
			}
			
			//associa l'array appena caricato ad una map di tutti i record
			$map[$record->getRecordId()]=$content;
		}
		$assoc_table['records']=$map;
		return $assoc_table;
	}
	
	/*Inserisce una riga nella tabella delle modifiche*/
	private function updateChangesTable($tableName,$recid,$action){
		/**/
		$query=$this->fmk->newAddCommand("Modifiche");
		$query->setField("Tabella",$tableName);
		$query->setField("Recid",$recid);
		$query->setField("Action",$action);
		
		$result=$query->execute();
		/**/
	}
	
	private function updateErrorTable($tableName,$action,$error){
		$query=$this->fmk->newAddCommand("Errori");
		$query->setField("Tabella",$tableName);
		$query->setField("Messaggio",$error);
		$query->setField("Action",$action);
		$query->setField("Utente",$this->currentUser);
		$query->setField("Data",date("m/d/y H:i:s"));
		
		$result=$query->execute();
	}
}

?>
